package com.uuah.server.transport;

import java.io.InputStream;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.uuah.exception.UuahRuntimeException;
import com.uuah.server.api.IExceptionPayload;
import com.uuah.server.api.IThreadSafeAccess;
import com.uuah.server.api.transport.IMessageAdapter;
import com.uuah.server.config.i18n.ServerMessages;
import com.uuah.utils.FileUtils;
import com.uuah.utils.UUID;

/**
 * <code>AbstractMessageAdapter</code> provides a base implementation for simple
 * message types that maybe don't normally allow for meta information, such as a
 * File or TCP.
 *
 * <p>
 * update record:updator，update time，update content and version
 * </p>
 *
 * @author <a href="jonny_quan@hotmail.com">jonny</a>
 * @date 2009 2009-3-27 下午02:49:42
 * @version 1.0.0
 */
public abstract class AbstractMessageAdapter implements IMessageAdapter,
		IThreadSafeAccess {
	private transient Logger logger = LoggerFactory
			.getLogger(AbstractMessageAdapter.class);

	protected MessagePropertiesContext properties = new MessagePropertiesContext();

	protected String encoding = FileUtils.DEFAULT_ENCODING;

	protected IExceptionPayload exceptionPayload;

	protected String id = UUID.getTimeBasedUUIDString();

	private transient AtomicReference ownerThread = null;

	private transient AtomicBoolean mutable = null;

	protected AbstractMessageAdapter() {
	}

	protected AbstractMessageAdapter(IMessageAdapter template) {
		if (null != template) {
			Iterator propertyNames = template.getPropertyNames().iterator();
			while (propertyNames.hasNext()) {
				String key = (String) propertyNames.next();
				try {
					setProperty(key, template.getProperty(key));
				} catch (Exception e) {
					throw new UuahRuntimeException(ServerMessages
							.failedToReadPayload(), e);
				}
			}

			encoding = template.getEncoding();
			exceptionPayload = template.getExceptionPayload();

			id = template.getUniqueId();
		}
	}

	public String toString() {
		assertAccess(READ);
		StringBuffer buf = new StringBuffer(120);
		buf.append(getClass().getName());
		buf.append("/" + super.toString());
		buf.append('{');
		buf.append("id=").append(getUniqueId());
		buf.append(", payload=").append(getPayload().getClass().getName());
		buf.append(", encoding=").append(getEncoding());
		buf.append(", exceptionPayload=").append(exceptionPayload);
		if (logger.isDebugEnabled()) {
			buf.append(", properties=").append(properties);
		}
		buf.append('}');
		return buf.toString();
	}

	public Map getScopeProperties(PropertyScope scope) {
		return properties.getScopedProperties(scope);
	}

	/** {@inheritDoc} */
	public void addProperties(Map props) {
		addProperties(props, properties.getDefaultScope());
		assertAccess(WRITE);
		if (props != null) {
			synchronized (props) {
				for (Iterator iter = props.entrySet().iterator(); iter
						.hasNext();) {
					Map.Entry entry = (Map.Entry) iter.next();
					setProperty((String) entry.getKey(), entry.getValue());
				}
			}
		}
	}

	/** {@inheritDoc} */
	public void addProperties(Map props, PropertyScope scope) {
		assertAccess(WRITE);
		if (props != null) {
			synchronized (props) {
				for (Iterator iter = props.entrySet().iterator(); iter
						.hasNext();) {
					Map.Entry entry = (Map.Entry) iter.next();
					setProperty((String) entry.getKey(), entry.getValue(),
							scope);
				}
			}
		}
	}

	/**
	 * A convenience method for extending classes to Set request scoped
	 * properties on the message properties that arrive on the request * should
	 * be set as request-scoped properties. These are read-only
	 *
	 * @param props
	 *            the properties to set
	 * @see com.uuah.server.transport.PropertyScope
	 */
	protected void addRequestProperties(Map props) {
		properties.addRequestProperties(props);
	}

	/** {@inheritDoc} */
	public void clearProperties() {
		assertAccess(WRITE);
		properties.clearProperties();
	}

	/** {@inheritDoc} */
	public Object removeProperty(String key) {
		assertAccess(WRITE);
		return properties.removeProperty(key);
	}

	/** {@inheritDoc} */
	public Object getProperty(String key) {
		assertAccess(READ);
		return properties.getProperty(key);
	}

	/** {@inheritDoc} */
	public Set getPropertyNames() {
		assertAccess(READ);
		return properties.getPropertyNames();
	}

	/** {@inheritDoc} */
	public void setProperty(String key, Object value) {
		assertAccess(WRITE);
		if (key != null) {
			if (value != null) {
				properties.setProperty(key, value);
			} else {
				logger.warn(
						"setProperty(key, value) called with null value; removing key: "
								+ key, new Throwable());
				properties.removeProperty(key);
			}
		} else {
			logger.warn(
					"setProperty(key, value) ignored because of null key for object: "
							+ value, new Throwable());
		}
	}

	/** {@inheritDoc} */
	public void setProperty(String key, Object value, PropertyScope scope) {
		assertAccess(WRITE);
		if (key != null) {
			if (value != null) {
				properties.setProperty(key, value, scope);
			} else {
				logger.warn(
						"setProperty(key, value) called with null value; removing key: "
								+ key, new Throwable());
				properties.removeProperty(key);
			}
		} else {
			logger.warn(
					"setProperty(key, value) ignored because of null key for object: "
							+ value, new Throwable());
		}
	}

	/** {@inheritDoc} */
	public String getUniqueId() {
		assertAccess(READ);
		return id;
	}

	/** {@inheritDoc} */
	public Object getProperty(String name, Object defaultValue) {
		assertAccess(READ);
		return properties.getProperty(name, defaultValue);
	}

	/** {@inheritDoc} */
	public int getIntProperty(String name, int defaultValue) {
		assertAccess(READ);
		return properties.getIntProperty(name, defaultValue);
	}

	public long getLongProperty(String name, long defaultValue) {
		assertAccess(READ);
		return properties.getLongProperty(name, defaultValue);
	}

	public double getDoubleProperty(String name, double defaultValue) {
		assertAccess(READ);
		return properties.getDoubleProperty(name, defaultValue);
	}

	public boolean getBooleanProperty(String name, boolean defaultValue) {
		assertAccess(READ);
		return properties.getBooleanProperty(name, defaultValue);
	}

	public String getStringProperty(String name, String defaultValue) {
		assertAccess(READ);
		return properties.getStringProperty(name, defaultValue);
	}

	public void setBooleanProperty(String name, boolean value) {
		assertAccess(WRITE);
		setProperty(name, Boolean.valueOf(value));
	}

	public void setIntProperty(String name, int value) {
		assertAccess(WRITE);
		setProperty(name, new Integer(value));
	}

	public void setLongProperty(String name, long value) {
		assertAccess(WRITE);
		setProperty(name, new Long(value));
	}

	public void setDoubleProperty(String name, double value) {
		assertAccess(WRITE);
		setProperty(name, new Double(value));
	}

	public void setStringProperty(String name, String value) {
		assertAccess(WRITE);
		setProperty(name, value);
	}

	public IExceptionPayload getExceptionPayload() {
		assertAccess(READ);
		return exceptionPayload;
	}

	public void setExceptionPayload(IExceptionPayload payload) {
		assertAccess(WRITE);
		exceptionPayload = payload;
	}

	public String getEncoding() {
		assertAccess(READ);
		return encoding;
	}

	public void setEncoding(String encoding) {
		assertAccess(WRITE);
		this.encoding = encoding;
	}

	public void release() {
		if (getPayload() instanceof InputStream) {
			IOUtils.closeQuietly((InputStream) getPayload());
		}
		properties.clearProperties();
	}

	public void assertAccess(boolean write) {
		if (AccessControl.isAssertMessageAccess()) {
			initAccessControl();
			setOwner();
			checkMutable(write);
		}
	}

	private void setOwner() {
		if (null == ownerThread.get()) {
			ownerThread.compareAndSet(null, Thread.currentThread());
		}
	}

	private void checkMutable(boolean write) {
		Thread currentThread = Thread.currentThread();
		if (currentThread.equals(ownerThread.get())) {
			if (write && !mutable.get()) {
				if (isDisabled()) {
					logger
							.warn("Writing to immutable message (exception disabled)");
				} else {
					throw newException("Cannot write to immutable message");
				}
			}
		} else {
			if (write) {
				if (isDisabled()) {
					logger
							.warn("Non-owner writing to message (exception disabled)");
				} else {
					throw newException("Only owner thread can write to message: "
							+ ownerThread.get() + "/" + Thread.currentThread());
				}
			} else {
				// access by another thread
				mutable.set(false);
			}
		}
	}

	protected IllegalStateException newException(String message) {
		IllegalStateException exception = new IllegalStateException(message);
		logger.warn("Message access violation", exception);
		return exception;
	}

	protected boolean isDisabled() {
		return !AccessControl.isFailOnMessageScribbling();
	}

	private synchronized void initAccessControl() {
		if (null == ownerThread) {
			ownerThread = new AtomicReference();
		}
		if (null == mutable) {
			mutable = new AtomicBoolean(true);
		}
	}

	public synchronized void resetAccessControl() {
		// just reset the internal state here as this method is explicitly
		// intended not to
		// be used from the outside
		if (ownerThread != null) {
			ownerThread.set(null);
		}
		if (mutable != null) {
			mutable.set(true);
		}
	}

	public IThreadSafeAccess newThreadCopy() {
		if (logger.isInfoEnabled()) {
			logger
					.info("The newThreadCopy method in AbstractMessageAdapter is being used directly. "
							+ "This code may be susceptible to 'scribbling' issues with messages. "
							+ "Please consider implementing the ThreadSafeAccess interface in the message adapter.");
		}
		return this;
	}

	public synchronized void setPayload(Object payload) {

	}
}
